/*
 * ArchivableImage.java
 *
 * Created on December 29, 2007, 10:35 PM
 */

package picto.core;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.HashMap;
import javax.imageio.*;
import picto.io.Archivable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import picto.io.ArchiveEntry;
import picto.io.ArchiveException;
import picto.io.ArchiveReader;
import picto.io.ArchiveWriter;
import picto.util.Base64;

/**
 * A canvas is a basic area that can be drawn
 * to.
 *
 * @author davedes
 */
public class ArchivableImage extends ImageState implements Archivable {
    
    public static final String PNG_FORMAT = "png";
    public static final String JPG_FORMAT = "jpg";
    public static final String TIFF_FORMAT = "tiff";
    public static final String DEFAULT_FORMAT = PNG_FORMAT;
    
    private String format = DEFAULT_FORMAT;
    protected String reference;
    protected HashMap<String, BufferedImage> cache = new HashMap<String, BufferedImage>();
            
    public ArchivableImage() {
        super();
    }
    
    public ArchivableImage(BufferedImage image) {
        super(image);
    }
    
    public ArchivableImage(int width, int height, int type) {
        super(width, height, type);
    }
                
    /**
     * Implemented by subclasses for cache support, otherwise
     * returns null.
     */
    protected BufferedImage getCachedImage(String reference) {
        return null;
    }
    
    /** 
     * Implemented by subclasses for cache support, otherwise
     * returns null.
     */
    protected BufferedImage putCachedImage(String reference, BufferedImage image) {
        return null;
    }
        
    public void read(ArchiveReader reader, Element root) throws ArchiveException {
        if (!"image-data".equals(root.getNodeName()))
            throw new IllegalArgumentException("Image must be parsed from <image-data> tag");
        
        //get the image format if there is one
        String fstr = root.getAttribute("format");
        if (fstr.length()!=0)
            format = fstr;
        else
            format = DEFAULT_FORMAT;
        
        setReference(root.getAttribute("ref"));
        
        //subclasses can use this to cache images
        this.image = getCachedImage(getReference());
        
        if (image==null) { //if no cached image, we read one
            InputStream data = null;
            boolean useRef = false;
            //first we attempt to load from a ref link
            if (getReference().length()!=0) {
                useRef = true;
                ArchiveEntry entry = reader.getEntry(getReference());
                if (entry!=null) {
                    data = entry.openStream();
                }
            }
            //the fallback is attempting to load from raw inline data
            if (data == null) {
                useRef = false;
                String enc = root.getTextContent();
                byte[] imgData = Base64.decode(enc.toCharArray());
                data = new ByteArrayInputStream(imgData);
            }
            //load up the image
            try { 
                image = ImageIO.read(data);
                if (image==null)
                    throw new ArchiveException("could not create image, unknown/corrupted format");
            } catch (IOException e) {
                String msg = "problem reading image";
                if (useRef)
                    msg += " '"+getReference()+"'";
                throw new ArchiveException(msg, e);
            }
            
            //now we cache the image
            putCachedImage(getReference(), image);
        }
        this.g2d = image.createGraphics();
    }
            
    public Element write(ArchiveWriter writer) throws ArchiveException {
        Document doc = writer.getDocument();
        Element root = doc.createElement("image-data");
        
        String format = getFormat().toLowerCase();
        String ref = getReference();

        //can't find ref, use "untitled_1.png"
        if (ref==null||ref.length()==0) {
            int num = writer.getEntryCount()+1;
            ref = "untitled_"+num;
        }
        ref += "."+format;
        if (writer.getEntry(ref) == null) {
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
                ImageIO.write(image, format, baos);
                baos.flush();
                baos.close();
                final byte[] input = baos.toByteArray();

                ArchiveEntry entry = new ArchiveEntry(ref, new ArchiveEntry.Provider() {
                    public InputStream createInputStream() {
                        return new ByteArrayInputStream(input);
                    }
                });
                writer.addEntry(entry);
            } catch (IOException ex) {
                throw new ArchiveException("Cannot write image data", ex);
            }
        }
        root.setAttribute("ref", ref);
        root.setAttribute("format", format);
        return root;
    }
    
    public String getFormat() {
        return format;
    }

    public void setFormat(String format) {
        this.format = format;
    }
    
    public ArchivableImage clone() {
        ArchivableImage img = new ArchivableImage();
        img.image = this.image;
        img.g2d = this.getGraphics();
        img.setReference(this.getReference());
        img.format = this.format;
        return img;
    }
    
    public ArchivableImage getScaledInstance(float scale) {
        return getScaledInstance(scale, null, DEFAULT_ARGB);
    }
        
    public ArchivableImage getScaledInstance(float scale, Color background, int type) {
        ArchivableImage clone = this.clone();
        
        int width = (int)(image.getWidth()*scale);
        int height = (int)(image.getHeight()*scale);
        
        BufferedImage scaled = new BufferedImage(width, height, type);
        Graphics2D g2d = scaled.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                            RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        if (background!=null) {
            g2d.setColor(background);
            g2d.fillRect(0, 0, width, height);
        }        
        AffineTransform at = AffineTransform.getScaleInstance(scale, scale);
        g2d.drawImage(image, at, null);
        
        //first we dispose the old clone's graphics
        clone.dispose(); 
        
        //then we give the clone new graphics and image
        clone.image = image;
        clone.g2d = g2d;
        
        return clone;
    }

    public String getReference() {
        return reference;
    }

    public void setReference(String reference) {
        this.reference = reference;
    }
}
